#include "paint.h"
#include "director.h"
#include "../../mathematics/bezier.h"
#include "../opengl/opengl.h"
#include "../model/mesh.h"


namespace jlib {


void PaintNode::draw (const mat4 &pv, const mat4 &p, const mat4 &v, const mat4 &m) {
  if (!this->_drawer)
    return;
  glBlendFunc(this->_srcFactor, this->_dstFactor);
  this->_baseTransform = pv * m;
  this->_userTransform = mat4::identity();
  this->_drawer(this);
}



void PaintNode::point (const vec2 &point, const rgba &color, float size) noexcept {
  this->points(&point, color, 1, size);
}

void PaintNode::points (const vec2 *points, const rgba &color, unsigned int count, float size) noexcept {
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points);
  glPointSize(size);
  glDrawArrays(GL_POINTS, 0, count);
}

void PaintNode::points (const vec2 *points, const rgba *colors, unsigned int count, float size) noexcept {
  auto shader = this->prepareVColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  glEnableVertexAttribArray(a_position);
  glEnableVertexAttribArray(a_color);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points);
  glVertexAttribPointer(a_color, 4, GL_FLOAT, GL_FALSE, 0, colors);
  glPointSize(size);
  glDrawArrays(GL_POINTS, 0, count);
}



void PaintNode::line (const vec2 &point1, const vec2 &point2, const rgba &color, float width) noexcept {
  vec2 points[2] = {point1, point2};
  this->lines(points, color, 2, GL_LINES, width);
}

void PaintNode::line (const vec2 &point1, const vec2 &point2, const rgba &color1, const rgba &color2, float width) noexcept {
  vec2 points[2] = {point1, point2};
  rgba colors[2] = {color1, color2};
  this->lines(points, colors, 2, GL_LINES, width);
}

void PaintNode::lines (const vec2 *point, const rgba &color, unsigned int count, GLenum mode, float width) noexcept {
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, point);
  glLineWidth(width);
  glDrawArrays(mode, 0, count);
}

void PaintNode::lines (const vec2 *point, const rgba *color, unsigned int count, GLenum mode, float width) noexcept {
  auto shader = this->prepareVColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  glEnableVertexAttribArray(a_position);
  glEnableVertexAttribArray(a_color);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, point);
  glVertexAttribPointer(a_color, 4, GL_FLOAT, GL_FALSE, 0, color);
  glLineWidth(width);
  glDrawArrays(mode, 0, count);
}



void PaintNode::bezier (const vec2 &point1, const vec2 &point2, const vec2 &point3, const rgba &color, float width, unsigned int segments) noexcept {
  vec2 points[segments + 1];
  auto s = 1.0F / segments;
  auto t = 0.f;
  for (auto i = 0; i < segments; ++i, t += s)
    points[i] = bezier::value(point1, point2, point3, t);
  points[segments] = bezier::value(point1, point2, point3, 1.0F);
  this->lines(points, color, segments + 1, GL_LINE_STRIP, width);
}

void PaintNode::bezier (const vec2 &point1, const vec2 &point2, const vec2 &point3, const vec2 &point4, const rgba &color, float width, unsigned int segments) noexcept {
  vec2 points[segments + 1];
  auto s = 1.0F / segments;
  auto t = 0.f;
  for (auto i = 0; i < segments; ++i, t += s)
    points[i] = bezier::value(point1, point2, point3, point4, t);
  points[segments] = bezier::value(point1, point2, point3, point4, 1.0F);
  this->lines(points, color, segments + 1, GL_LINE_STRIP, width);
}



void PaintNode::circle (const vec2 &center, float radius, const rgba &color) noexcept {
  std::vector<vec2> points;
  Mesh::circleTriangleStrip(points, radius, center, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}

void PaintNode::circle (const vec2 &center, float radius, const rgba &color, float width) noexcept {
  std::vector<vec2> points;
  Mesh::circleLineLoop(points, radius, center, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glLineWidth(width);
  glDrawArrays(GL_LINE_LOOP, 0, points.size());
}



void PaintNode::oval (const vec2 &center, float radiusX, float radiusY, const rgba &color) noexcept {
  std::vector<vec2> points;
  Mesh::ovalTriangleStrip(points, radiusX, radiusY, center, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}

void PaintNode::oval (const vec2 &center, float radiusX, float radiusY, const rgba &color, float width) noexcept {
  std::vector<vec2> points;
  Mesh::ovalLineLoop(points, radiusX, radiusY, center, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glLineWidth(width);
  glDrawArrays(GL_LINE_LOOP, 0, points.size());
}



void PaintNode::rect (const box2 &rect, const rgba &color) noexcept {
  vec2 points[4] {rect.xy(), rect.Xy(), rect.XY(), rect.xY()};
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points);
  glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void PaintNode::rect (const box2 &rect, const rgba &color, float width) noexcept {
  vec2 points[4] {rect.xy(), rect.Xy(), rect.XY(), rect.xY()};
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points);
  glLineWidth(width);
  glDrawArrays(GL_LINE_LOOP, 0, 4);
}



void PaintNode::roundRect (const box2 &rect, const vec4 &radius, const rgba &color) noexcept {
  std::vector<vec2> points;
  Mesh::roundRectTriangleStrip(points, rect, radius, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}

void PaintNode::roundRect (const box2 &rect, const vec4 &radius, const rgba &color, float width) noexcept {
  std::vector<vec2> points;
  Mesh::roundRectLineLoop(points, rect, radius, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glLineWidth(width);
  glDrawArrays(GL_LINE_LOOP, 0, points.size());
}

void PaintNode::roundRect (const box2 &rect, const vec4 &radiusX, const vec4 &radiusY, const rgba &color) noexcept {
  std::vector<vec2> points;
  Mesh::roundRectTriangleStrip(points, rect, radiusX, radiusY, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}

void PaintNode::roundRect (const box2 &rect, const vec4 &radiusX, const vec4 &radiusY, const rgba &color, float width) noexcept {
  std::vector<vec2> points;
  Mesh::roundRectLineLoop(points, rect, radiusX, radiusY, this->_accuracy);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glLineWidth(width);
  glDrawArrays(GL_LINE_LOOP, 0, points.size());
}



void PaintNode::ring (const vec2 &origin, const vec2 &radius, const rgba &color, float startAngle, float endAngle) noexcept {
  std::vector<vec2> points;
  Mesh::ringTriangleStrip(points, radius, origin, d2r(startAngle), d2r(endAngle), d2r(this->_accuracy));
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}

void PaintNode::ring (const vec2 &origin, const vec2 &radiusX, const vec2 &radiusY, const rgba &color, float startAngle, float endAngle) noexcept {
  std::vector<vec2> points;
  Mesh::ringTriangleStrip(points, radiusX, radiusY, origin, d2r(startAngle), d2r(endAngle), d2r(this->_accuracy));
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, points.data());
  glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
}



void PaintNode::polygon (const vec2 *vertices, unsigned int count, const rgba &color) noexcept {
  std::vector<unsigned short> indices;
  Mesh::polygonTriangles(indices, vertices, count, 0.0F);
  auto shader = this->prepareUColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  shader->u4f(u_color, color);
  glEnableVertexAttribArray(a_position);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
  glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.data());
}

void PaintNode::polygon (const vec2 *vertices, unsigned int count, const rgba &color, float width) noexcept {
  this->lines(vertices, color, count, GL_LINE_LOOP, width);
}

void PaintNode::polygon (const vec2 *vertices, const rgba *colors, unsigned int count) noexcept {
  std::vector<unsigned short> indices;
  Mesh::polygonTriangles(indices, vertices, count, 0.0F);
  auto shader = this->prepareVColorShader();
  shader->u4m(u_pvm, this->_baseTransform * this->_userTransform);
  glEnableVertexAttribArray(a_position);
  glEnableVertexAttribArray(a_color);
  glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
  glVertexAttribPointer(a_color, 4, GL_FLOAT, GL_FALSE, 0, colors);
  glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.data());
}

void PaintNode::polygon (const vec2 *vertices, const rgba *colors, unsigned int count, float width) noexcept {
  this->lines(vertices, colors, count, GL_LINE_LOOP, width);
}



Shader *PaintNode::prepareUColorShader () noexcept {
  if (!this->_ucolorShader)
    this->_ucolorShader = this->getRoot()->getShaderManager()->get(UColorShaderName, UColorVShader, UColorFShader);
  Shader *shader = this->_ucolorShader;
  debug_assert(shader && shader->valid());
  shader->use();
  return shader;
}

Shader *PaintNode::prepareVColorShader () noexcept {
  if (!this->_vcolorShader)
    this->_vcolorShader = this->getRoot()->getShaderManager()->get(VColorShaderName, VColorVShader, VColorFShader);
  Shader *shader = this->_vcolorShader;
  debug_assert(shader && shader->valid());
  shader->use();
  return shader;
}



const char *PaintNode::UColorVShader = R"(#version 430 core
layout(location = 0) uniform mat4 u_pvm;
layout(location = 0) in vec4 a_position;
void main () {
  gl_Position = u_pvm * a_position;
}
)";
const char *PaintNode::UColorFShader = R"(#version 430 core
layout(location = 1) uniform vec4 u_color;
void main () {
  gl_FragColor = u_color;
}
)";
const std::string PaintNode::UColorShaderName = "PaintNode::ucolor";



const char *PaintNode::VColorVShader = R"(#version 430 core
layout(location = 0) uniform mat4 u_pvm;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec4 a_color;
out vec4 v_color;
void main () {
  gl_Position = u_pvm * a_position;
  v_color = a_color;
}
)";
const char *PaintNode::VColorFShader = R"(#version 430 core
in vec4 v_color;
void main () {
  gl_FragColor = v_color;
}
)";
const std::string PaintNode::VColorShaderName = "PaintNode::vcolor";



} // namespace jlib